package net.cathackers.devel.scmp.chat.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import net.cathackers.devel.scmp.factories.SCMPFactory;

import org.apache.log4j.Logger;

// Deeply influenced by OpenFire implementation
public class TLSConfig {

    private static KeyStore keyStore_;
    private static SSLContext c2sContext_;
    private static SSLServerSocketFactory c2sFactory_;
    private static String keyStoreLocation_;
    private static String keypass_;
    private static String algorithm_;
    private static String storeType_;
    private static String c2sTrustStoreLocation_;
    private static String c2sTrustpass_;
    private static KeyStore c2sTrustStore_;
    private static Logger logger_;
    
    static {
	logger_ = SCMPFactory.getLogger(TLSConfig.class);
	// TODO make those defaults through configuration
        algorithm_ = "TLS";
        storeType_ = "jks";

        // Get the keystore location. The default location is security/keystore
        keyStoreLocation_ = "security" + File.separator + "keystore";

        // Get the keystore password. The default password is "redhat".
        keypass_ = "redhat";

        // Get the truststore location for c2s connections
        c2sTrustStoreLocation_ = "security" + File.separator + "truststore.client";
        c2sTrustpass_ = "redhat";

        // Load c2s TrustStore
        try {
                keyStore_ = KeyStore.getInstance(storeType_);
                keyStore_.load(new FileInputStream(keyStoreLocation_), keypass_.toCharArray());
            	logger_.debug("Loading Client Truststore " + c2sTrustStoreLocation_);
                c2sTrustStore_ = KeyStore.getInstance(storeType_);
                c2sTrustStore_.load(new FileInputStream(c2sTrustStoreLocation_), c2sTrustpass_.toCharArray());
        }
        catch (Exception e) {
            logger_.warn("Failed to load trust store...");
            try {
                c2sTrustStore_ = KeyStore.getInstance(storeType_);
                logger_.debug("Creating empty trust store");
                c2sTrustStore_.load(null, c2sTrustpass_.toCharArray());
            }
            catch (Exception ex) {
                logger_.error("SSLConfig startup problem.\n" +
                        "  storeType: [" + storeType_ + "]\n" +
                        "  c2sTrustStoreLocation: [" + c2sTrustStoreLocation_ + "]\n" +
                        "  c2sTrustPass: [" + c2sTrustpass_ + "]", e);
                c2sTrustStore_ = null;
            }
        }
        resetFactory();
    }
    
    private static void resetFactory() {
        try {
//            String algorithm = JiveGlobals.getProperty("xmpp.socket.ssl.algorithm", "TLS");

            c2sContext_ = SSLContext.getInstance(algorithm_);

            KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyFactory.init(keyStore_, TLSConfig.getKeyPassword().toCharArray());

            TrustManagerFactory c2sTrustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            c2sTrustFactory.init(c2sTrustStore_);

            c2sContext_.init(keyFactory.getKeyManagers(),
                c2sTrustFactory.getTrustManagers(),
                new java.security.SecureRandom());

            c2sFactory_ = c2sContext_.getServerSocketFactory();
            

        }
        catch (Exception e) {
            logger_.error("TLLConfig factory setup problem.\n" +
                    "  storeType: [" + storeType_ + "]\n" +
                    "  keyStoreLocation: [" + keyStoreLocation_ + "]\n" +
                    "  keypass: [" + keypass_ + "]\n" +
                    "  c2sTrustStoreLocation: [" + c2sTrustStoreLocation_ + "]\n" +
                    "  c2sTrustpass: [" + c2sTrustpass_ + "]", e);
            keyStore_ = null;
            c2sTrustStore_ = null;
            c2sFactory_ = null;
        }
    }
    
    public static KeyStore initializeKeyStore() {
        try {
            keyStore_ = KeyStore.getInstance(storeType_);
            keyStore_.load(null, keypass_.toCharArray());
        }
        catch (Exception e) {
            logger_.error("Unable to initialize keystore: ", e);
        }
        return keyStore_;
    }
    
    public static String getKeyPassword() {
	return keypass_;
    }
    
    public static KeyStore getKeyStore() throws IOException {
        if (keyStore_ == null) {
            throw new IOException();
        }
        return keyStore_;
    }
    public static KeyStore getc2sTrustStore() {
	return c2sTrustStore_;
    }
    
    public static ServerSocket createc2sServerSocket(int port, InetAddress ifAddress) throws
    IOException {
        if (c2sFactory_ == null) {
            throw new IOException();
        }
        else {
            return c2sFactory_.createServerSocket(port, -1, ifAddress);
        }
    }
    
    public static String getc2sTrustPassword() {
	return c2sTrustpass_;
    }
    /**
     * Get the SSLServerSocketFactory for c2s connections
     *
     * @return the SSLServerSocketFactory for c2s connections
     */
    public static SSLServerSocketFactory getc2sServerSocketFactory() {
        return c2sFactory_;
    }
    /**
     * Get the SSLContext for c2s connections
     *
     * @return the SSLContext for c2s connections
     */
    public static SSLContext getc2sSSLContext() {
        return c2sContext_;
    }
    public static KeyManager[] getKeyManagers(KeyStore keystore, String keypass) {
	KeyManager[] keyManagers;
	try {
		if (keystore == null) {
			keyManagers = null;
		} else {
			KeyManagerFactory keyFactory = KeyManagerFactory
					.getInstance(KeyManagerFactory.getDefaultAlgorithm());
			if (keypass == null) {
				keypass = TLSConfig.getKeyPassword();
			}

			keyFactory.init(keystore, keypass.toCharArray());
			keyManagers = keyFactory.getKeyManagers();
		}
	} catch (KeyStoreException e) {
		keyManagers = null;
		logger_.error("TLSConfig startup problem.\n" +
            "  the keystore is corrupt", e);
	} catch (NoSuchAlgorithmException e) {
		keyManagers = null;
		logger_.error("TLSConfig  startup problem.\n" +
            "  the keystore type doesn't exist (not provided or configured with your JVM)", e);
	} catch (UnrecoverableKeyException e) {
		keyManagers = null;
		logger_.error("TLSConfig  startup problem.\n" +
            "  the keystore could not be opened (typically the password is bad)", e);
	} 
	return keyManagers;
    }
    public static TrustManager[] getTrustManagers(KeyStore truststore,
		String trustpass) {
	TrustManager[] trustManagers;
	try {
		if (truststore == null) {
			trustManagers = null;
		} else {
			TrustManagerFactory trustFactory = TrustManagerFactory
					.getInstance(TrustManagerFactory.getDefaultAlgorithm());
			if (trustpass == null) {
				trustpass = TLSConfig.getc2sTrustPassword();
			}

			trustFactory.init(truststore);

			trustManagers = trustFactory.getTrustManagers();
		}
	} catch (KeyStoreException e) {
		trustManagers = null;
		logger_.error("TLSConfig startup problem.\n" +
        "  the keystore is corrupt", e);
	} catch (NoSuchAlgorithmException e) {
		trustManagers = null;
		logger_.error("TLSConfig startup problem.\n" +
        "  the keystore type doesn't exist (not provided or configured with your JVM)", e);
	}
	return trustManagers;
    }
}
